/* Copyright by GD GameDuell GmbH, All Rights Reserved
 * This document is strictly confidential and sole property of GD GameDuell GmbH, Berlin, Germany
 */

package de.gameduell.loader.src {
	import flash.events.SecurityErrorEvent;
	import flash.events.IOErrorEvent;

	import de.gameduell.framework.application.IApplication;
	import de.gameduell.framework.application.Settings;
	import de.gameduell.framework.asset.AssetEvent;
	import de.gameduell.framework.asset.AssetGroup;
	import de.gameduell.framework.asset.BitmapAsset;
	import de.gameduell.framework.asset.IAsset;
	import de.gameduell.framework.asset.SWFAsset;
	import de.gameduell.framework.asset.TextAsset;
	import de.gameduell.framework.asset.XMLAsset;
	import de.gameduell.framework.debug.Debug;
	import de.gameduell.framework.locale.Locale;
	import de.gameduell.framework.util.FlashVars;

	import flash.display.MovieClip;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.external.ExternalInterface;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.navigateToURL;
	import flash.system.Capabilities;
	import flash.system.Security;
	import flash.utils.getTimer;

	/**
	 * Set  up all needed variables for showing the stage and loading
	 * specified assets.
	 * This class represents the document class of:
	 * <b>src.de.gameduell.loader.fla.loaderv3.fla</b>
	 */
	public class ApplicationLoader extends MovieClip {

		/** handled file formats */
		public static const COMMUNICATION:String = "communication";

		protected const SWF:String = "swf";
		protected const JPG:String = "jpg";
		protected const txt:String = "txt";
		protected const xml:String = "xml";
		protected const PNG:String = "png";
		
		protected const LOADER:String = "loader";
		protected const LOADERGFX:String = "loadergfx";
		protected const STANDALONE:String = "standalone";
		protected const OFFLINE:String = "offline";
		protected const ONLINE:String = "online";
		protected const SETTINGS:String = "settings";
		protected const CONDITION:String = "condition";
		protected const APPLICATION:String = "application";
		protected const CONFIG_FILE:String = "config.xml";
		protected const DEFAULT_LANG:String = "de";		
		protected const LOCALIZED:String = "localized";
		protected const USE_SAME_DOMAIN:String = "useSameDomain";
		
		protected var appURL:String;
		protected var baseURL:String;
		protected var isStandalone:Boolean;
		protected var isOnline:Boolean;
		protected var configAsset:XMLAsset;
		protected var settings:Settings;
		protected var config:XML;
		protected var locale:Locale;
		protected var _applicationAssets:AssetGroup;
		protected var _loaderAssets:AssetGroup;
		protected var _langAsset:XMLAsset;
		protected var _generalLangAsset:XMLAsset;
				
		private var startTime:int;

		/**
		 * Setting scale mode and language according to external parameters or using defaults.
		 * Set up all loader related gfx and place them in the middle of the stage.
		 * Initializes asset loading and start loading the main config file of a application.
		 */
		public function ApplicationLoader(){
			traceToFrontend("loader start");
			startTime = getTimer();
			init();
			locale = Locale.getInstance();
		}

		protected function init():void {
			catchVars();
			
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.showDefaultContextMenu = false;
			stage.align = StageAlign.TOP_LEFT;
			stage.stageFocusRect = false;
					
			if(FlashVars.getKeyValue(FlashVars.LANGUAGE) == FlashVars.NO_VALUE) {
				FlashVars.setKeyValue(FlashVars.LANGUAGE, DEFAULT_LANG);
			}
			FlashVars.setKeyValue(FlashVars.LANGUAGE, FlashVars.getKeyValue("language").toLowerCase());

			extractUrl();
			traceHeader();

			configAsset = new XMLAsset("config", appURL + CONFIG_FILE);
			configAsset.addEventListener(AssetEvent.COMPLETE, configLoaded);
			configAsset.addEventListener(AssetEvent.LOAD_ERROR, loadError);
			configAsset.load();
			isStandalone = false;
		}

		/**
		 * loads the variables from the hml into the Flashvar Object
		 * can be overriden for alternative variable source (AIR)
		 */
		protected function catchVars():void {
			FlashVars.catchVars(root.loaderInfo.parameters);
		}

		/**
		 * Extracts all needed parts of the loader url.
		 * <b>baseURL</b> loader url without any url parameters and file name
		 * <b>appURL</b> path to application related assets
		 */
		protected function extractUrl():void {
			var _url:String = root.loaderInfo.url;
			_url = _url.substr(0, _url.indexOf("?"));
			_url = _url.substr(0, _url.lastIndexOf("/") + 1);
			
			isOnline = _url.indexOf("http://") == 0 || _url.indexOf("https://") == 0; 
			appURL = _url + FlashVars.getKeyValue(FlashVars.XML_FOLDER) + "/";
			baseURL = _url;
			FlashVars.setKeyValue(FlashVars.APP_URL, appURL);
		}

		/**
		 * Called after an application config file was loaded.
		 * Exctract all raw settings and starts parsing it.
		 * 
		 * @param event		config loaded event
		 */		
		protected function configLoaded(event:AssetEvent):void {
			try{  
				config 		 = XML(event.asset.data);
				settings 	 = new Settings(config.child(SETTINGS));
				isStandalone = settings.standalone;
				
				var loaderList:XMLList = config.child(LOADER);
				_loaderAssets = new AssetGroup(LOADER, "");
				if(settings.useGeneralLangFile) {
					loaderList.appendChild(new XML(getGeneralLang()));
					
				}
				loaderList.appendChild(new XML("<lang>lang/" + settings.gameType + "_" + FlashVars.getKeyValue("language") + ".xml</lang>"));
				
				fillAssetGroup(loaderList,_loaderAssets);
				
				_loaderAssets.addEventListener(AssetEvent.COMPLETE, initLoader);
				_loaderAssets.addEventListener(AssetEvent.LOAD_ERROR, loadError);
				_loaderAssets.load();
			}catch(e:Error){
				Debug.trace("Error loading config: " + e.getStackTrace(), Debug.ERROR);
				traceToFrontend("Error loading config");
			}
		}

		/**
		 * Initializes the loader bar. Each application can use an own loader bar.
		 * The loader bar asset can be configured via the config.xml.
		 */
		protected function initLoader(event:AssetEvent):void {
			var loaderGroup:AssetGroup = AssetGroup(event.asset);
			var gfxAsset:SWFAsset = SWFAsset(loaderGroup.getAssetById("loadergfx"));

			if(loaderGroup.assetExists("general_lang")){
				_generalLangAsset = XMLAsset(loaderGroup.getAssetById("general_lang"));
				locale.addXML(_generalLangAsset.xmlData);
			}
			_langAsset = XMLAsset(loaderGroup.getAssetById("lang"));
			locale.addXML(_langAsset.xmlData);
			
			var contentClass:Class = Class(gfxAsset.applicationDomain.getDefinition("content"));
			var content:MovieClip = new contentClass() as MovieClip;
			locale.localizeChildren(content);
			addChild(content);
			var loaderBar:ILoaderBar = ILoaderBar(gfxAsset.data);
			_applicationAssets = createAssetsFromConfig();

			loaderBar.init(_applicationAssets,content);
			loaderBar.addEventListener(AssetEvent.COMPLETE, complete);
			_applicationAssets.addEventListener(AssetEvent.LOAD_ERROR, loadError);
			_applicationAssets.load();
		}

		protected function loadError(event:AssetEvent):void {
			Debug.trace("loadError: " + event.asset.url, Debug.ERROR);
			traceToFrontend("Error loading " + event.asset.url);
//			reportLoadingError(event.asset);
		}
		
//		protected function reportLoadingError(anAsset:IAsset):void {
//			var assetURL:String = anAsset.url;
//			var assetId:String = anAsset.id;
//			var assetError:String = anAsset.error;
//			var lang:String   = FlashVars.getKeyValue("language");
//			var gameId:String = FlashVars.getKeyValue("gameid");
//			var mid:String    = FlashVars.getKeyValue("mid");
//			var xmlf:String   = FlashVars.getKeyValue("xmlfolder");
//			var gchost:String = FlashVars.getKeyValue("gchost");
//			
//			var _url:String = root.loaderInfo.url;
//			_url = _url.substr(0, _url.indexOf("?"));
//			_url = _url.substr(0, _url.lastIndexOf("/") + 1);
//			var loaderURL:String = _url;
//			
//			var os:String = Capabilities.os;
//			var version:String = Capabilities.version;
//			
//			var reportString:String = "xmlfolder=" + xmlf +
//									  "&assetId="  + assetId +
//									  "&assetURL=" + assetURL +
//									  "&loaderURL=" + loaderURL + 
//									  "&lang=" + lang + 
//									  "&gameId=" + gameId + 
//									  "&gchost=" + gchost + 
//									  "&mid=" + mid + 
//									  "&os=" + os +
//									  "&version=" + version +
//									  "&assetError=" + assetError;
//		
//			var reportURL:String = "http://report.gameduell.de/logger/reportException.jsp?" + reportString;
//			reportURL = FlashVars.getKeyValue("report") == FlashVars.NO_VALUE ? reportURL : FlashVars.getKeyValue("report");
//
//			var reportRequest:URLRequest = new URLRequest(reportURL + reportString);
//			reportRequest.method = "GET";
//			var myLoader:URLLoader = new URLLoader();
//
//			myLoader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
//            myLoader.addEventListener(Event.COMPLETE, loaderCompleteHandler);
//            myLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler);
//			myLoader.load(reportRequest);
//		}
		
		private function errorHandler(event:Event):void {
		}

		private function loaderCompleteHandler(event:Event):void {
		}

		/**
		 * If a loader runs the first time, the current debugConsole output
		 * will be cleared, and a copyright messages appears.
		 * Setting up the logheads name. Loghead represents the TAB in which the
		 * debug output runs to. If the frontend don't hold any FlashVars.user parameter,
		 * "default" appears as TAB name.
		 */
		public function traceHeader():void {
			Debug.logHead = FlashVars.getKeyValue("user") == FlashVars.NO_VALUE ? "default" : FlashVars.getKeyValue("user");
			Debug.clear();
			Debug.trace("# <font color='#FF6600'>V3 - LOADER - Copyright (c) 2003-2011 GD GameDuell GmbH, All Rights Reserved.</font> #<br>", Debug.DEFAULT);
		}

		/**
		 * @return path to an general language content file,
		 * which typically will be added to the loader queue.
		 */
		protected function getGeneralLang():String {
			if(isOnline){
				return "<general_lang>../lang/general" + "_" + FlashVars.getKeyValue("language") + ".xml</general_lang>";
			}else{
				return "<general_lang>../../libraries/src/de/gameduell/game/lang/general" + "_" + FlashVars.getKeyValue("language") + ".xml</general_lang>";
			}
		}

		protected function getUrls(node:XML):String {
			var nodeName:String = String(node.name());
			if(nodeName == ONLINE || nodeName == STANDALONE || nodeName == LOADERGFX) {
				return baseURL + node.text();
			} else {
				var localized:Boolean = getAttribute(node, LOCALIZED) != null;
				if(localized) {
					return appURL + String(node.text()).split("*").join(FlashVars.getKeyValue("language"));
				} else {
					return appURL + node.text();
				}
			}
			return null;
		}

		/**
		 * Parsing all nodes of an application config (config.xml).
		 */
		protected function createAssetsFromConfig():AssetGroup {
			var assetsList:XMLList = config.child("assets");
			var comList:XMLList    = config.child("communication");
			var assets:AssetGroup  = new AssetGroup("assets", appURL);
			var comType:String;
			
			switch(true) {
				case isOnline && isStandalone:	
					comType = STANDALONE;
					break;
				case isOnline && !isStandalone:
					comType = ONLINE;
					break;
				case !isOnline:
					comType = OFFLINE;
					break;
			}
			if(comList.toString() != "") {
				var comNode:XML = XML(comList.descendants(comType));
				assets.addAsset(createAssetFromXML(comNode, COMMUNICATION));
			}			
			fillAssetGroup(assetsList,assets);
			return assets;
		}

		protected function fillAssetGroup(assetsList:XMLList,assetGroup:AssetGroup):void {
			try {
				var nodeNames:Array = getNodeIds(assetsList.children());
				for(var i in nodeNames) {
					var equalNodes:Array = getNodesWithId(nodeNames[i], assetsList.children());
					var defaultNode:XML = null;
					var foundMatch:Boolean = false;
					for(var j in equalNodes) {				
						var node:XML = equalNodes[j];
						var conditionExists:Boolean = getAttribute(node, CONDITION) != "";
						if(conditionExists) {
							if(getAttribute(node, CONDITION) == "default") {
								defaultNode = node;
							}
							if(checkCondition(node)) {
								assetGroup.addAsset(createAssetFromXML(node));
								foundMatch = true;	
							}
						}else{
							assetGroup.addAsset(createAssetFromXML(node));	
							foundMatch = true;	
						}
					}
					if(!foundMatch) {
						assetGroup.addAsset(createAssetFromXML(defaultNode));
					}
				}
			}catch(e:Error) {
				Debug.trace("ApplicationLoader::createAssetsFromConfig: error: " + e.getStackTrace(), Debug.ERROR);
				traceToFrontend("Error creating assets:: " + node);
			}	
		}

		public function getNodeIds(xmlList:XMLList):Array {
			var nodeNames:Array = new Array();
			for each(var node:XML in xmlList) {
				var nodeName:String = node.name();
				var exists:Boolean = false;
				for(var i in nodeNames) {
					if(nodeName == nodeNames[i]) {
						exists = true;
					}
				}
				if(!exists) {
					nodeNames.push(nodeName);
				}
			}
			return nodeNames;
		}

		public function getNodesWithId(id:String,xmlList:XMLList):Array {
			var matches:Array = new Array();
			for(var i in xmlList) {
				var node:XML = XML(xmlList[i]);
				if(id == node.name()) {
					matches.push(node);
				}
			}
			return matches;
		}

		/**
		 * If an asset in config.xml contains a condition attribute, this
		 * function compares condition values with FlashVars values.
		 * 
		 * @param node 	a xml node which contains all condition related stuff
		 */
		protected function checkCondition(node:XML):Boolean {
			var aCondition:Array = getAttribute(node, CONDITION).split("=");
			if(aCondition.length == 2){
				return FlashVars.getKeyValue(String(aCondition[0])) == String(aCondition[1]);
			}
			return false;
		}

		protected function createAssetFromXML(node:XML, aName:String = null):IAsset {
			var name:String = aName == null ? String(node.name()) : aName;
			var url:String  = getUrls(node);
			var type:String = getAssetTypeByURL(url);
			var usd:Boolean = getAttribute(node, USE_SAME_DOMAIN) != "false" ;
			return createAsset(type, name, url, usd);
		}

		/**
		 * Gets an attribute of a xmlNode.
		 * 
		 * @param node			a xml node which contains attributes
		 * @param attribute		name of the attribute to search for
		 * @return an attributes value as String
		 */
		protected function getAttribute(node:XML, attribute:String):String {
			return node.attribute(attribute).toString();
		}

		/**
		 * Creates an asset according to a given file type.
		 * 
		 * @param type	contains the file extension
		 * @param name	represents the id of an asset. Typically this is the name of a xml node of config.xml
		 * @param url	full path to the file
		 * 
		 * @return	an asset or null if no asset was created	
		 */
		protected function createAsset(type:String, name:String, url:String,usd:Boolean = true):IAsset {
			switch(type) {
				case SWF:
					return new SWFAsset(name, url, usd);
				case JPG:
				case PNG:
					return new BitmapAsset(name, url, usd);
				case txt:
					return new TextAsset(name, url);
				case xml:
					return new XMLAsset(name, url);
			}
			return null;
		}

		/**
		 * Handler for asset loading complete - initalizes the main application
		 * If all assets were loaded this function will be called.
		 * 
		 * @param event		all assets were loaded
		 */
		protected function complete(event:Event):void {
			startApplication();
		}
		
		protected function startApplication():void {
			if(_generalLangAsset != null){
				_applicationAssets.addAsset(_generalLangAsset);
			}
			_applicationAssets.addAsset(_langAsset);
			var appAsset:IAsset = _applicationAssets.getAssetById(APPLICATION);
			var application:IApplication = IApplication(appAsset.data);
			FlashVars.setKeyValue(FlashVars.LOADING_TIME, String(getTimer() - startTime));
			application.init(stage, _applicationAssets, settings, isStandalone || !isOnline);
			traceToFrontend("application start");
		}

		/**
		 * Determines a file type by a file URI.
		 * 
		 * @param url		a String which contains the full path.
		 * @return String	file extention of null if noch such extention was found
		 */
		protected function getAssetTypeByURL(url:String):String {
			var tempUrl:String = url.toLowerCase();
			var typeExists:Boolean = tempUrl.lastIndexOf(".") != -1;
			return	typeExists ? tempUrl.substr(tempUrl.lastIndexOf(".") + 1, tempUrl.length) : null;	
		}
		
		/**
		 * call javascript function flashtrace
		 * @param params:String parameter to be passed to the JS function
		 */		
		public function traceToFrontend(params:String):void {
			callJavaScript("flashtrace", params);
		}

		/**
		 * call javascript function
		 * @param params:String parameter to be passed to the JS function
		 * @param method:String JS function to call
		 */	
		public function callJavaScript(funcName:String, ...args):void {
			try {
				if(Security.sandboxType != Security.LOCAL_WITH_FILE) {
					if(ExternalInterface.available) {
						var argArray:Array = new Array();
						argArray.push(funcName);
						for (var j:int = 0;j < args.length; j++) {
							argArray.push(args[j]);
						}
						var f:Function = ExternalInterface.call;
						f.apply(null, argArray);  
					} else {
						Debug.trace("ApplicationLoader::callJavaScript: ExternalInterface is not available ", Debug.WARNING);
						for (var i:uint = 0;i < args.length; i++) {
							args[i] = "'" + args[i] + "'";
						}
						var argsStr:String = args.join(",");
						var str:String = "javascript:" + funcName + "(" + argsStr + ")";	
						var req:URLRequest = new URLRequest(str);
						navigateToURL(req, "_self");
					}
				} else {
					Debug.trace("ApplicationLoader::callJavaScript: call JS function via ExternalInterface blocked because sandboxType is Security.LOCAL_WITH_FILE ", Debug.WARNING);
				}
			} catch (er:Error) {
				Debug.trace("ApplicationLoader::callJavaScript: unable to call JS function via ExternalInterface -> error: " + er, Debug.ERROR);
			}
		}
	}
}